<?php
/**
* Collection of models
* 
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* 
* @copyright Copyright (c) 2008-2009 Ne8, LLC <josh.ribakoff@gmail.com>
* @license New BSD License
* @version 0.1
* @category Shuffler
* @package Shuffler_Collection
*/
class Shuffler_Collection implements IteratorAggregate, Shuffler_Model_IdObserver
{
    protected $position = 0;
    protected $models = array();
    protected $modelIds = array();
    
    /**
    * Set an array of objects in the constructor
    * 
    * @param array of objects to initialize with
    * @param string optional type, if passed an exception will be thrown if any class is added that isnt an instance of this
    * 
    * @todo tests for the $type param
    */             
    public function __construct( array $array = null, $type = NULL )
    {
        if( is_null( $array ) )
        {
            $array = array();
        }
        $this->position = 0;
        if( !is_null( $type ) )
        {
            foreach( $array as $model )
            {
                Shuffler::assertInstance( $model, $type );
            }
        }
        if( is_null( $array ) )
        {
            $array = array();
        }
        foreach( $array as $model )
        {
            $this->addModel( $model );
        }
    }
    
    public function toArray()
    {
        $ar = array();
        foreach( $this as $model )
        {
            $ar[ $model->getId() ] = $model;
        }
        return $ar;
    }
    
    public function toTitleArray()
    {
        $array = array();
        foreach( $this->getModels() as $model )
        {
            $array[ $model->getId() ] = (string)$model;
        }
        return $array;
    }
    
    public function toIdArray()
    {
        $array = array();
        foreach( $this->getModels() as $model )
        {
            $array[] = $model->getId();
        }
        return $array;
    }
    
    public function getIterator()
    {
        return $this->getModels();
    }
    
    /**
    * Adds a certain model from the collection
    * @param Shuffler_Model to add
    */
    public function addModel( Shuffler_Model $model )
    {
        if( $this->hasModel( $model ) )
        {
            return false;
        }
        array_push( $this->models, $model );
        $temp = array_slice( $this->models, -1, 1, true);
        $this->modelIds[ get_class( $model )][ $model->getId() ] = key( $temp );
        $model->addIdObserver( $this );
    }
    
    /**
    * This method is notified when one of this collection's models is assigned an ID ( that is, persisted for the first time ).
    * It is called when the id changes, and re-indexes the modelIds array
    */
    public function notify( Shuffler_Model $model )
    {
        $this->modelIds = array();
        foreach( $this->getModels() as $key => $model )
        {
            $this->modelIds[ get_class( $model )][ $model->getId() ] = $key;
        }
    }
    
    /**
    * Removes a certain model from the collection
    * @param Shuffler_Model to remove
    */
    public function removeModel( Shuffler_Model $model )
    {
        unset( $this->models[ $this->getArrayIndexForModel( $model ) ] );
        unset( $this->modelIds[ get_class( $model )][ $model->getId() ] );
        return $this;
    }
    
    public function removeModels()
    {
        $this->models = array();
        $this->modelIds = array();
    }
    
    /**
    * Get the model at a specific index in the results array
    * 
    * @param integer index
    * 
    * @return Shuffler_Model
    */
    public function getModelAtIndex( $index )
    {
        return $this->models[ $index ];
    }
    
    /**
    * Searches $this->models and returns the index of the passed model arguments, returns false if we dont have it
    * @param Shuffler_Model to search for  
    * @return mixed, integer or false on failure
    */
    protected function getArrayIndexForModel( Shuffler_Model $model )
    {
        if( !$model instanceof Shuffler_Model )
        {
            throw new Exception();
        }
        return $this->getModelId( $model );
    }
    
    /**
    * Get the modelID for a model
    * 
    * @return mixed integer id, or boolean false on failure
    */
    protected function getModelId( $model )
    {
        $modelIds = $this->getModelIds( get_class( $model ) );
        if( !is_array( $modelIds ) )
        {
            return false;
        }
        if( !isset( $modelIds[ $model->getId() ] ) )
        {
            return false;
        }
        return $modelIds[ $model->getId() ];
    }
    
    /**
    * Get modelID array for a given model class
    * 
    * @param string classname
    * @return array of integer ( model ids )
    */
    protected function getModelIds( $modelClass )
    {
        if( !isset( $this->modelIds[ $modelClass ] ) || !is_array( $this->modelIds[ $modelClass ] ) )
        {
            $this->modelIds[ $modelClass ] = array();
        }
        return $this->modelIds[ $modelClass ];
    }
    
    
    /**
    * @return bool
    */
    public function contains( $compare )
    {
        if( $compare instanceof Shuffler_Model )
        {
            return $this->containsModel( $compare );
        }
        else if ( $compare instanceof Shuffler_Collection )
        {
           return $this->containsCollection( $compare );
        }
        throw new Exception( get_class( $compare ) . ' given, Shuffler_Model | Shuffler_Collection expected' );
    }
    
    /**
    * Check if the model is in this collection
    * @param Shuffler_Model to search for  
    * @return bool
    */
    public function containsModel( Shuffler_Model $model )
    {
        return $this->getArrayIndexForModel( $model ) !== false;
    }
    
    /**
    * Check if this collection contains another
    * 
    * @param Shuffler_Collection collection to compare with
    * @return bool
    */
    public function containsCollection( Shuffler_Collection $collection )
    {
        foreach( $collection as $model )
        {
            // a single item not equals means the whole collection is not equal
            if( !$this->containsModel( $model ) )
            {
                return false;
            }
        }
        return true;
    }
    
    /**
    * @return ArrayIterator
    */
    public function getModels()
    {
        return new ArrayIterator( $this->models );
    }
    
    /**
    * Get # of models this collection currently has
    * 
    * @return int count
    */
    public function count()
    {
        return count( $this->models );
    }
    
    /**
    * Merge a collection into this one
    * 
    * @param Shuffler_Collection collection to merge in
    */
    public function merge( Shuffler_Collection $collection )
    {
        foreach( $collection as $model )
        {
            $this->addModel( $model );
        }
        return $this;
    }           
    
    /**
    * Check if this collection is equal to another
    * 
    * @param Shuffler_Collection collection to compare with
    * @return bool
    */
    public function equals( Shuffler_Collection $collection )
    {
        return( $this->contains( $collection ) && $collection->contains( $this ) );
    }
    
    public function isEmpty()
    {
        return 0 === $this->count();
    }   
    
    public function hasModel( Shuffler_Model $model )
    {
        return ( $this->getArrayIndexForModel( $model ) !== false );
    }

}